`include "defines.v"

module uart_tx #
(
    parameter   CLK_FREQ    = 50_000_000, // 时钟频率50MHz
    parameter   BSP         = 9600, // 波特率9600
    parameter   PARITY      = `NONE, // 奇偶校验位
    parameter   DATA_BITS   = 8, // 数据位bit
    parameter   STOP_BITS   = 1 // 停止位bit
)
(
    input                           clk_50m,
    input                           rst_n,
    input  [DATA_BITS - 1 : 0]      data, // 待发送的数据
    input                           enable, // 发送命令
    output                          busy, // 忙信号，高电平时表示正在发送数据，此时不应该再接收新数据
    output reg                      tx
);

    reg ifparity        = 0; // 是否有校验位
    reg parity_value    = 0; // 校验位的值

    localparam CNT_MAX  = CLK_FREQ / BSP;
    reg [31:0] cnt      = 0;

    reg [2:0] cstate        = `FSM_IDLE;
    reg [3:0] num           = 0;

    // 1、定义一个标志位，用于锁住enable和data
    reg flag            = 0;
    reg [DATA_BITS - 1 : 0] data_tmp = 0;
    always @(posedge clk_50m or negedge rst_n) begin
        if (!rst_n) flag <= 0;
        else if (enable) begin
            flag <= 1;
            data_tmp <= data;
        end else if (cstate == `FSM_STOP && cnt == CNT_MAX - 1) flag <= 0;
        else flag <= flag;
    end

    assign busy = flag;

    // 2、cnt计数
    always @(posedge clk_50m or negedge rst_n) begin
        if (!rst_n) cnt <= 0;
        else if (cnt == CNT_MAX - 1 || flag == 0) cnt <= 0;
        else if (flag) cnt <= cnt + 1;
        else cnt <= cnt;
    end

    // 3、使用状态机发送数据
    always @(posedge clk_50m or negedge rst_n) begin
        if (!rst_n) begin
            cstate <= `FSM_IDLE;
            tx <= 1; // 空闲状态时输出为高电平
            num <= 0;
        end else begin
            case (cstate)
                `FSM_IDLE: begin
                    tx <= 1; // 空闲状态
                    if (flag) cstate <= `FSM_START;
                    else cstate <= `FSM_IDLE;
                end
                `FSM_START: begin
                    tx <= 0; // 发送起始位
                    if (cnt == CNT_MAX - 1) cstate <= `FSM_DATA;
                    else cstate <= `FSM_START;
                end
                `FSM_DATA: begin
                    tx <= data_tmp[num]; // 发送数据位
                    if (cnt == CNT_MAX - 1) begin
                        num <= num + 1;
                    end else begin
                        num <= num;
                    end

                    // 数据位发送完成
                    if (cnt == CNT_MAX - 1 && num == DATA_BITS - 1) begin
                        num <= 0;
                        cstate <= ifparity ? `FSM_PARITY : `FSM_STOP;
                    end else begin
                        cstate <= `FSM_DATA;
                    end
                end
                `FSM_PARITY: begin
                    tx <= parity_value; // 发送校验位
                    if (cnt == CNT_MAX - 1) cstate <= `FSM_STOP;
                    else cstate <= `FSM_PARITY;
                end
                `FSM_STOP: begin
                    tx <= 1; // 发送停止位
                    if (STOP_BITS == 1 && cnt == CNT_MAX - 1) cstate <= `FSM_IDLE;
                    else if (STOP_BITS == 2) begin
                        if (num == STOP_BITS - 1 && cnt == CNT_MAX - 1) begin
                            cstate <= `FSM_IDLE;
                            num <= 0;
                        end
                        else if (cnt == CNT_MAX - 1) num <= num + 1;
                        else num <= num;
                    end
                    else cstate <= `FSM_STOP;
                end
                default: ;
            endcase
        end
    end

    // 4、设置校验位
    always @(*) begin
        if (!rst_n) begin
            ifparity <= 0;
            parity_value <= 0;
        end else begin
            case (PARITY)
                `NONE: begin ifparity <= 0; parity_value <= 0; end
                `ODD: begin ifparity <= 1; parity_value <= ~(^data_tmp); end
                `EVEN: begin ifparity <= 1; parity_value <= ^data_tmp; end
                `MARK: begin ifparity <= 1; parity_value <= 1; end
                `SPACE: begin ifparity <= 1; parity_value <= 0; end
                default: begin ifparity <= 0; parity_value <= 0; end
            endcase
        end
    end

endmodule  //uart_tx